#ifndef __CPLANE_H__
#define __CPLANE_H__

#include "StdDefs.h"
#include "CMathFixed.h"
#include "CVector3d.h"
#include "CLineSegment3d.h"
#include "CSpace.h"
#include "CVector2d.h" 



class CPlane
{
public:
	enum AxisAlignedPlane
	{
		IJ = 0,
		JK = 1,
		KI = 2
	};

	CPlane(){}
	CPlane( const fixed* abcd );
	CPlane( fixed a, fixed b, fixed c, fixed d );
	CPlane( const CVector3d& n, fixed d );
	CPlane( const CVector3d& v0, const CVector3d& v1, const CVector3d& v2 );
	CPlane( const CPlane& p );
	~CPlane(){}

	static AxisAlignedPlane		GetClosestAxisAlignedPlane( const CVector3d& v0, const CVector3d& v1, const CVector3d& v2 );
	static AxisAlignedPlane		GetClosestAxisAlignedPlane( const CVector3d& normal );
	static void					ProjectToAAPlane( CPlane::AxisAlignedPlane aaplane, const CVector3d& v, CVector2d& out );
	
	static boolean				IsCoplanar( const CVector3d& v0, const CVector3d& v1, const CVector3d& v2, const CVector3d& v3 );

// hacky, looking for suggestions
static void					Solve( fixed &x, fixed& y, fixed a1, fixed b1, fixed c1, fixed a2, fixed b2, fixed c2 );
	// solve for ax + by + c form = 0

	CPlane&						Clear();
	CPlane&						Set( fixed a, fixed b, fixed c, fixed d );
	CPlane&						Set( const CVector3d& n, fixed d );
	CPlane&						Set( const CVector3d& v0, const CVector3d& v1, const CVector3d& v2 );
	CPlane&						Set( const CPlane& p );
	CPlane&						Normalize();

	fixed						GetSignedDistanceOfPoint( const CVector3d& v ) const;
	fixed						GetSignedDistanceOfPoint( const CVector3d&v, CVector3d& ptOnPlane ) const;
	CSpace::Relativity			DetermineSpace( fixed signedDistanceToPoint ) const;

	boolean						Intersects( const CVector3d& v0, const CVector3d& v1, CVector3d& outPoint, fixed& u ) const;
	boolean						Intersects( const CLineSegment3d& seg, CVector3d& outPoint, fixed& u ) const;
	boolean						Intersects( const CPlane& p, CVector3d& lineNormal ) const;
	boolean						Intersects( const CPlane& p, CVector3d& lineNormal, CVector3d& pt ) const;
	boolean						Intersects( const CPlane& p1, const CPlane& p2, CVector3d& pt ) const;
	boolean						Intersects( const CVector3d& segA, const CVector3d& segB, fixed r ) const;

	boolean						IsCoplanar( const CVector3d& p ) const;

	AxisAlignedPlane			GetClosestAxisAlignedPlane() const;

	CPlane&						operator=( const fixed* abcd );

	CPlane&						operator+=( fixed val );
	CPlane&						operator-=( fixed val );
	CPlane&						operator*=( fixed val );
	CPlane&						operator/=( fixed val );

	friend CPlane				operator+( const CPlane& p, fixed val );
	friend CPlane				operator+( fixed val, const CPlane& p );
	friend CPlane				operator-( const CPlane& p, fixed val );
	friend CPlane				operator-( fixed val, const CPlane& p );
	friend CPlane				operator*( const CPlane& p, fixed val );
	friend CPlane				operator*( fixed val, const CPlane& p );
	friend CPlane				operator/( const CPlane& p, fixed val );
	friend CPlane				operator/( fixed val, const CPlane& p );

	CVector3d					m_n;
	fixed						m_d;
};


inline
boolean CPlane::Intersects( const CLineSegment3d& seg, CVector3d& outPoint, fixed& u ) const
{
	return Intersects( seg.m_v0, seg.m_v1, outPoint, u );
}


inline
boolean CPlane::Intersects( const CVector3d& segA, const CVector3d& segB, fixed r ) const
{
	fixed d1 = GetSignedDistanceOfPoint( segA );
	
	if ( CMathFixed::Abs( d1 ) <= r )
		return TRUE;

	fixed d2 = GetSignedDistanceOfPoint( segB );

	if ( CMathFixed::Abs( d2 ) <= r )
		return TRUE;

	if ( d1 * d2 < 0 ) // signed distances are different signs, ie the segment pieces the plane
		return TRUE;

	return FALSE;
}


inline
boolean CPlane::IsCoplanar( const CVector3d& v0, const CVector3d& v1, const CVector3d& v2, const CVector3d& v3 )
{
	CVector3d x = v2 - v0;
	CVector3d y = v1 - v0;
	CVector3d z = v3 - v2;
	return ( CMathFixed::Abs( x * ( y.Cross(z) ) ) <= CMathFixed_FloatToFixed( 0.001f ) /* EPSILON */ );
}


inline
void CPlane::ProjectToAAPlane( CPlane::AxisAlignedPlane aaplane, const CVector3d& v, CVector2d& out )
{
	if ( aaplane == CPlane::IJ )
	{
		out.m_i = v.m_i;
		out.m_j = v.m_j;
	}
	else
	{
		if ( aaplane == CPlane::JK )
		{
			out.m_i = v.m_j;
			out.m_j = v.m_k;
		}
		else
		{
			out.m_i = v.m_k;
			out.m_j = v.m_i;
		}
	}
}


inline
CPlane::AxisAlignedPlane CPlane::GetClosestAxisAlignedPlane( const CVector3d& v0, const CVector3d& v1, const CVector3d& v2 )
{
	CVector3d normal = v1 - v0;
	normal.Cross( v2 - v0 );
	return GetClosestAxisAlignedPlane( normal );
}



 inline 
CPlane::CPlane( fixed a, fixed b, fixed c, fixed d )
{ 
	m_n.Set( a, b, c ); 
	m_d = d; 
}


inline
CPlane::CPlane( const CVector3d& n, fixed d )
{
	m_n = n;
	m_d = d;
}


inline 
CPlane::CPlane( const CPlane& p )
{
	m_n = p.m_n;
	m_d = p.m_d;
}


inline
void CPlane::Solve( fixed &x, fixed& y, fixed a1, fixed b1, fixed c1, fixed a2, fixed b2, fixed c2 )
{
	fixed oneoverdenom = CMathFixed::Div( CMathFixed::One, CMathFixed::Mul( b2, a1 ) - CMathFixed::Mul( a2, b1 ) );
	x = CMathFixed::Mul( CMathFixed::Mul( b1, c2 ) - CMathFixed::Mul( b2, c1 ), oneoverdenom );
	y = CMathFixed::Mul( CMathFixed::Mul( a2, c1 ) - CMathFixed::Mul( c1, a1 ), oneoverdenom );
}


inline
CPlane& CPlane::Clear()
{
	m_n.Clear(); 
	m_d = 0; 

	return *this;
}


inline
CPlane& CPlane::Set( fixed a, fixed b, fixed c, fixed d )
{
	m_n.Set( a, b, c );
	m_d = d;

	return *this;
}


inline
CPlane& CPlane::Set( const CVector3d& n, fixed d )
{
	m_n = n;
	m_d = d;

	return *this;
}


inline 
CPlane& CPlane::Set( const CPlane& p )
{
	m_n = p.m_n;
	m_d = p.m_d;

	return *this;
}


inline
fixed CPlane::GetSignedDistanceOfPoint( const CVector3d& v ) const
{
	return m_n * v - m_d; 
}


inline
fixed CPlane::GetSignedDistanceOfPoint( const CVector3d&v, CVector3d& ptOnPlane ) const
{
	fixed distance = GetSignedDistanceOfPoint( v );
	ptOnPlane = v + distance * m_n;
	return distance;
}


inline
boolean CPlane::IsCoplanar( const CVector3d& p ) const
{
	return ( CMathFixed::Abs( GetSignedDistanceOfPoint(p) ) <= CMathFixed_FloatToFixed( 0.001f ) /* EPSILON */ );
}


inline
CPlane::AxisAlignedPlane CPlane::GetClosestAxisAlignedPlane() const
{
	return GetClosestAxisAlignedPlane( this->m_n );
}


inline 
CPlane operator+( const CPlane& p, fixed val )
{ 
	return CPlane( p.m_n.m_i + val, p.m_n.m_j + val, p.m_n.m_k + val, p.m_d + val ); 
}


inline 
CPlane operator+( fixed val, const CPlane& p )
{ 
	return CPlane( val + p.m_n.m_i, val + p.m_n.m_j, val + p.m_n.m_k, val + p.m_d ); 
}


inline 
CPlane operator-( const CPlane& p, fixed val )
{ 
	return CPlane( p.m_n.m_i - val, p.m_n.m_j - val, p.m_n.m_k - val, p.m_d - val ); 
}


inline 
CPlane operator-( fixed val, const CPlane& p )
{ 
	return CPlane( val - p.m_n.m_i, val - p.m_n.m_j, val - p.m_n.m_k, val - p.m_d ); 
}


inline
CPlane operator*( const CPlane& p, fixed val )
{
	return CPlane( CMathFixed::Mul( p.m_n.m_i, val ), CMathFixed::Mul( p.m_n.m_k, val ),
				   CMathFixed::Mul( p.m_n.m_j, val ), CMathFixed::Mul( p.m_d, val ) );
}


inline
CPlane operator*( fixed val, const CPlane& p )
{
	return CPlane( CMathFixed::Mul( val, p.m_n.m_i ), CMathFixed::Mul( val, p.m_n.m_j ),
				   CMathFixed::Mul( val, p.m_n.m_k ), CMathFixed::Mul( val, p.m_d ) );
}


inline
CPlane operator/( const CPlane& p, fixed val )
{
	return CPlane( CMathFixed::Div( p.m_n.m_i, val ), CMathFixed::Div( p.m_n.m_k, val ),
				   CMathFixed::Div( p.m_n.m_j, val ), CMathFixed::Div( p.m_d, val ) );
}


inline
CPlane operator/( fixed val, const CPlane& p )
{
	return CPlane( CMathFixed::Div( val, p.m_n.m_i ), CMathFixed::Div( val, p.m_n.m_j ),
				   CMathFixed::Div( val, p.m_n.m_k ), CMathFixed::Div( val, p.m_d ) );
}




#endif // __CPLANE_H__